const Spin = @This();

/// Is the lock held?
locked: std.atomic.Value(bool) = .init(false),

/// Name of lock. For debugging
name: []const u8,
/// The cpu holding the lock.
cpu: ?*Cpu = null,

pub fn init(name: []const u8) Spin {
    return .{ .name = name };
}

/// Check whether this cpu is holding the lock. Interrupts must be off.
pub fn holding(self: *Spin) bool {
    return self.locked.load(.monotonic) and self.cpu == Cpu.this();
}

/// Acquire the lock. Loops (spins) until the lock is acquired.
pub fn acquire(self: *Spin) void {
    const this = Cpu.push(); // disable interrupts to avoid deadlock.

    if (self.holding()) @panic("deadlock");

    // compare exchange is sorta semantically equivalent to the following atomically:
    // ```
    //   current = load(ptr, failure_ordering)
    //   if current != compare:
    //     return current
    //
    //   store(ptr, exchange, success_ordering)
    //   return null
    // ```
    while (self.locked.cmpxchgWeak(false, true, .acquire, .acquire) == true) {
        std.atomic.spinLoopHint();
    }

    // Record info about lock acquisition for holding() and debugging.
    self.cpu = this;
}

/// Release the lock.
pub fn release(self: *Spin) void {
    if (!self.holding()) @panic("release");

    const this = self.cpu.?;
    self.cpu = null;

    // Release the lock, equivalent to self.locked = false.
    self.locked.store(false, .release);
    this.pop();
}

const std = @import("std");
const os = @import("../os.zig");
const Cpu = os.proc.Cpu;
